﻿using System;
using System.Data;
using System.IO;
using System.Threading.Tasks;

namespace TestAgentDatabases
{
    /// <summary>
    /// Base class for running database tests
    /// </summary>
    abstract class DatabaseTest
    {
        public const string TableName = "MarketOrder";

        public int Agents { get; set; }
        public int AgentsPerDatabase { get; set; }
        public int InsertsPerAgent { get; set; }
        //public int UpdatesPerAgent { get; set; }
        public string DatabaseDirectory { get; set; }
        //public string BackupDirectory { get; set; }

        public void EnsureDirectoryExists()
        {
            if (string.IsNullOrEmpty(DatabaseDirectory)) return;

            if (!Directory.Exists(DatabaseDirectory))
            {
                Directory.CreateDirectory(DatabaseDirectory);
            }
        }

        public virtual void DeleteDatabases()
        {
            foreach (var file in Directory.GetFiles(DatabaseDirectory))
            {
                File.Delete(file);
            }
        }

        public void CreateDatabases()
        {
            int databases = GetDatabaseCount();

            var tasks = new Task[databases];
            for (int i = 0; i < databases; i++)
            {
                tasks[i] = new Task(CreateDatabase, i);
                tasks[i].Start();
            }
            Task.WaitAll(tasks);
        }

        public int GetDatabaseCount()
        {
            int divRem ;
            int databases = Math.DivRem(Agents, AgentsPerDatabase, out divRem);
            if (divRem > 0)
                databases++;
            return databases;
        }

        public void InsertData()
        {
            var tasks = new Task[Agents];
            for (int i = 0; i < Agents; i++)
            {
                tasks[i] = new Task(InsertData, i);
                tasks[i].Start();
            }
            Task.WaitAll(tasks);
        }

        protected void CreateDatabase(object state)
        {
            var databaseId = (int)state;
            using (IDbConnection connection = CreateDatabaseConnection(databaseId))
            {
                connection.Open();
                CreateDatabase(connection, databaseId);
                CreateOrdersTable(connection, databaseId);
            }
            //Console.WriteLine("Database {0} created", state);
        }

        protected virtual void CreateDatabase(IDbConnection connection, int databaseId)
        {
        }

        protected void InsertData(object state)
        {
            int agentId = (int) state;
            int databaseId = agentId%GetDatabaseCount();
            using (IDbConnection connection = GetDatabaseConnection(databaseId))
            {
                connection.Open();
                IDbCommand command = null;
                for (int i = 0; i < InsertsPerAgent; i++)
                {
                    InsertOrder(databaseId, i, connection, ref command);
                }
                if (command != null) command.Dispose();
            }
            //Console.WriteLine("Agent {0} finished inserting", state);
        }

        protected abstract void InsertOrder(int databaseId, int orderIndex, IDbConnection connection, ref IDbCommand command);

        protected IDbDataParameter AddParameter(IDbCommand dbCommand, string parameterName, DbType dbType)
        {
            IDbDataParameter param = dbCommand.CreateParameter();
            param.DbType = dbType;
            param.ParameterName = parameterName;
            dbCommand.Parameters.Add(param);
            return param;
        }

        private void CreateOrdersTable(IDbConnection connection, int databaseId)
        {
            using (IDbCommand dbCommand = connection.CreateCommand())
            {
                dbCommand.CommandText = GetCreateTableSQL(databaseId);
                dbCommand.ExecuteNonQuery();
            }
        }

        protected abstract string GetCreateTableSQL(int databaseId);

        protected abstract IDbConnection CreateDatabaseConnection(int databaseIdId);

        protected abstract IDbConnection GetDatabaseConnection(int databaseId);
    }
}